﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Diagnostics;
using Microsoft.Surface.Presentation.Controls;
using System.Windows.Media.Imaging;

namespace Master
{
    /*
     * This class is used to build a intersection inside a WPF window.
     * The intersection carries properties for the strategy and the trafficlight timers.
     * 
     * @Creator: T.T. Dusseldorp
     * @Changes made by:
     *      - A.J. Janssen
     *      - V.L. de Jager
     */
    public class InterSection : Label
    {
        /*************************
         * DEPENDENCY PROPERTIES *
         *************************/
        public static readonly DependencyProperty strategyProperty, timeGreenProperty, 
            timeRedProperty, timeOrangeProperty;

        public String Strategy
        {
            set { SetValue(strategyProperty, value); }
            get { return (String)GetValue(strategyProperty); }
        }

        public int TimeGreen
        {
            set { SetValue(strategyProperty, value); }
            get { return (int)GetValue(strategyProperty); }
        }

        public int TimeOrange
        {
            set { SetValue(strategyProperty, value); }
            get { return (int)GetValue(strategyProperty); }
        }

        public int TimeRed
        {
            set { SetValue(strategyProperty, value); }
            get { return (int)GetValue(strategyProperty); }
        }

        private static object CoerceStrategy(DependencyObject d, object value)
        {
            String strategyName = (String)value;
            InterSection interSection = (InterSection)d;
            if (!strategyExists(strategyName) || 
                (interSection.roadDictionaryReady && !TrafficLightStrategyFactory.createStrategy(strategyName).canBeUsedOnInterSection(interSection))) 
            {
                if (interSection.roadDictionaryReady && !TrafficLightStrategyFactory.createStrategy(interSection.Strategy).canBeUsedOnInterSection(interSection))
                    return "SimpleStrategy";
                else return interSection.Strategy;
            }
            return value;
        }

        private static void strategyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            String strategyName = (String)e.NewValue;
            iTrafficLightStrategy newStrategy = null;
            newStrategy = TrafficLightStrategyFactory.createStrategy(strategyName);
            ((InterSection)d).setStrategy(newStrategy);
        }

        private static void trafficLightTimePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            InterSection interSection = (InterSection)d;
            iTrafficLightStrategy newStrategy = null;
            newStrategy = TrafficLightStrategyFactory.createStrategy((string)d.GetValue(strategyProperty));
            ((InterSection)d).setStrategy(newStrategy);
        }

        private static Boolean strategyExists(String strategyName)
        {
            try
            {
                iTrafficLightStrategy newStrategy = TrafficLightStrategyFactory.createStrategy(strategyName);
            }
            catch (InvalidOperationException exc)
            {
                showInvalidStrategyName(exc);
                return false;
            }
            return true;
        }

        /*
         * Inside this static constructor all metadata and events will be set for the depency properties. 
         */
        static InterSection()
        {
            FrameworkPropertyMetadata strategyPropertyMetadata = new FrameworkPropertyMetadata("SimpleStrategy", 
                new PropertyChangedCallback(InterSection.strategyPropertyChanged));
            strategyPropertyMetadata.CoerceValueCallback = new CoerceValueCallback(CoerceStrategy);
            strategyProperty = DependencyProperty.Register("Strategy", typeof(String), typeof(InterSection), strategyPropertyMetadata);
            
            FrameworkPropertyMetadata TimeRedPropertyMetaData = new FrameworkPropertyMetadata(500,
                new PropertyChangedCallback(InterSection.trafficLightTimePropertyChanged));
            timeRedProperty = DependencyProperty.Register("TimeRed", typeof(int), typeof(InterSection), TimeRedPropertyMetaData);
            FrameworkPropertyMetadata TimeGreenPropertyMetaData = new FrameworkPropertyMetadata(7500,
                new PropertyChangedCallback(InterSection.trafficLightTimePropertyChanged));
            timeGreenProperty = DependencyProperty.Register("TimeGreen", typeof(int), typeof(InterSection), TimeGreenPropertyMetaData);
            FrameworkPropertyMetadata TimeOrangePropertyMetaData = new FrameworkPropertyMetadata(5000,
                new PropertyChangedCallback(InterSection.trafficLightTimePropertyChanged));
            timeOrangeProperty = DependencyProperty.Register("TimeOrange", typeof(int), typeof(InterSection), TimeOrangePropertyMetaData);
        }

        /***********
        * WARNINGS *
        ************/

        /**
         * This gets called when the user attempts to create a strategy that does not exist (example: Intersection.Strategy = "AirPlaneStrategy")
         * */
        private static void showInvalidStrategyName(Exception e)
        {
            Trace.WriteLine(e.StackTrace);
        }

        /**
         * This gets called when the user attempts to set a strategy that is not compatible with this intersection (example: Intersection.Strategy = "ThreeLaneStrategy" where Intersecion has only 2 lanes)
         * */
        private static void showInvalidStrategyChoice(Exception e)
        {
            Trace.WriteLine(e.StackTrace);
        }

        /*************
         * VARIABLES *
         *************/
        private iTrafficLightStrategy currentStrategy;
        private Dictionary<Side, List<Road>> roadDictionary = new Dictionary<Side, List<Road>>();
        public Boolean roadDictionaryReady = false;

        /********************
         * PUBLIC FUNCTIONS *
         ********************/
        public TrafficMarshal getTrafficMarshal()
        {
            if (isTurn()) 
                return null;
            return currentStrategy.getTrafficMarshal();
        }

        private void setStrategy(iTrafficLightStrategy newStrategy)
        {
            int timeRed = (int)GetValue(timeRedProperty);
            int timeOrange = (int)GetValue(timeOrangeProperty);
            int timeGreen = (int)GetValue(timeGreenProperty);
            if (!roadDictionaryReady) return;
            stopStrategy();
            currentStrategy = newStrategy;
            newStrategy.start(this, new TrafficLightDetails(timeRed, timeOrange, timeGreen));
        }

        private void stopStrategy()
        {
            if (currentStrategy != null)
                currentStrategy.stop();
        }

        /*
         * Function to stop the current traffic light strategy at the intersection.
         */
        public void stopStrategy(object sender, EventArgs e)
        {
            stopStrategy();
        }

        /*
         * Function is used to set up the intersection.
         * The road dictionary used by the intersection will be set and the trafficlight strategy will be started.
         */
        public void init(object sender, EventArgs e)
        {
            setupRoadDictionary();
            roadDictionaryReady = true;
            if (Background == Brushes.Transparent)
                this.Background = Brushes.Gray;
            if (isTurn())
                setTurn();
            else
            {
                setStrategy(TrafficLightStrategyFactory.createStrategy(Strategy));
                setMenu();
            }
        }

        private void setTurn()
        {
            //set lanes to turn-lane
            foreach (List<Road> roadList in roadDictionary.Values)
                foreach (Road road in roadList)
                    foreach (Lane lane in road.getLanes())
                        lane.setTurnLane();
            //stop strategy
            stopStrategy();
        }

        private void setMenu()
        {
            ElementMenu a = new ElementMenu();
            a.ActivationMode = ElementMenuActivationMode.HostInteraction;
            a.ActivationHost = this;
            a.VerticalAlignment = System.Windows.VerticalAlignment.Center;
            a.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
            a.Orientation = 0;
            foreach (String strategy in TrafficLightStrategyFactory.methodDictionary.Keys) {
                if (TrafficLightStrategyFactory.createStrategy(strategy).canBeUsedOnInterSection(this))
                a.Items.Add(createMenuItem(strategy, strategy)); }
             double x = Canvas.GetLeft(this);
            double y = Canvas.GetTop(this);
            Canvas.SetTop(a, y);
            Canvas.SetLeft(a, x);
            Canvas.SetZIndex(a, 11);
            Map.INSTANCE.Children.Add(a);
        }

        private ElementMenuItem createMenuItem(String header, String commandParameter)
        {
            Image iconss = new Image();
            iconss.Source = new BitmapImage(new Uri(@"/Master;component/Images/stoplicht.png", UriKind.Relative));
            ElementMenuItem lightMenu = new ElementMenuItem();
            lightMenu.Click += setStrategy;
            lightMenu.CommandParameter = commandParameter;
            lightMenu.Header = header;
            lightMenu.Icon = iconss;
            return lightMenu;
        }


        private void setStrategy(object sender, RoutedEventArgs e)
        {
            ElementMenuItem menuitem = (ElementMenuItem)sender;
            String newStrat = menuitem.CommandParameter.ToString();
            InterSection intersect = Map.INSTANCE.getInterSectionAtPoint(Toolkit.getCenterPoint((FrameworkElement)menuitem.Parent));
            intersect.Strategy = newStrat;

        }

        public Dictionary<Side, List<Road>> getRoadDictionary()
        {
            return roadDictionary;
        }

        /*
         * Returns all road at a side from the intersection
         */
        public List<Road> getRoadsBySide(Side side)
        {
            return roadDictionary[side];
        }

        /*
         * Returns the exit lane for a single car at the intersection at a direction
         */
        public Lane getExitLane(DirectionData directionData, Car car)
        {
            if (directionData.getDirection() == Direction.FORWARD)
                return getFowardExitLane(directionData.getOrientation(), car.getTrueCenter());
            return getTurnExitLane(directionData, car);
        }

        /*
         * Function defines if a intersection is three lane intersection.
         */
        public Boolean isThreeLaneIntersection()
        {
            foreach (Side side in Enum.GetValues(typeof(Side)))
            {
                if (!roadDictionary.ContainsKey(side)) return false;
                int entryLanes = 0;
                foreach (Road road in roadDictionary[side])
                    foreach (Lane lane in road.getLanes())
                        if (lane.hasTrafficLight())
                            entryLanes++;
                if (entryLanes != 3) return false;
            }
            return true;
        }

        /*
         * Function defines if intersction is a t-junction.
         */
        public Boolean isTJunction()
        {
            return (roadDictionary.Keys.Count == 3);
        }

        /*
         * Function defines if intersection is a turn.
         */
        public Boolean isTurn()
        {
            return (roadDictionary.Keys.Count == 2);
        }

        /*********************
         * PRIVATE FUNCTIONS *
         *********************/
        private void setupRoadDictionary()
        {
            //Get x and y
            double x = Canvas.GetLeft(this);
            double y = Canvas.GetTop(this);

            //getTopSide
            Point pointer = new Point(x + 1, y - 10);
            addRoad(Side.TOP, pointer);

            //getRightSide
            pointer = new Point(x + Width + 10, y + 1);
            addRoad(Side.RIGHT, pointer);

            //getBottomSide
            pointer = new Point(x + 1, y + Height + 10);
            addRoad(Side.BOTTOM, pointer);

            //getLeftSide
            pointer = new Point(x - 10, y + 1);
            addRoad(Side.LEFT, pointer);
        }

        private void addRoad(Side side, Point pointer)
        {
            List<Road> roadList = getRoadsBySide(side, pointer);
            if (roadList.Count > 0)
                roadDictionary.Add(side, getRoadsBySide(side, pointer));
        }

        private List<Road> getRoadsBySide(Side side, Point pointer)
        {
            List<Road> roadList = new List<Road>();
            double startX = pointer.X;
            double startY = pointer.Y;
            Road road;
            switch (side)
            {
                case Side.BOTTOM:
                case Side.TOP:
                    while ((road = findRoad(roadList, pointer)) != null)
                    {
                        roadList.Add(road);
                        pointer.X += road.ActualWidth;
                    }
                    break;
                case Side.LEFT:
                case Side.RIGHT:
                    while ((road = findRoad(roadList, pointer)) != null)
                    {
                        roadList.Add(road);
                        pointer.Y += road.ActualHeight;
                    }
                    break;
            }
            roadList = setupTrafficLights(roadList, side);
            return roadList;
        }

        private Road findRoad(List<Road> roadList, Point pointer)
        {
            Road road = Map.INSTANCE.getRoadAtPoint(pointer);
            if (road != null && !roadList.Contains(road))
                return road;
            return null;
        }

        private List<Road> setupTrafficLights(List<Road> roadList, Side side)
        {
            foreach (Road road in roadList) foreach (Lane lane in road.getLanes())
            {
                if ((side == Side.TOP && lane.Orientation == 180) ||
                    (side == Side.RIGHT && lane.Orientation == 270) ||
                    (side == Side.BOTTOM && lane.Orientation == 0) ||
                    (side == Side.LEFT && lane.Orientation == 90))
                {
                    lane.setTrafficLight();
                    lane.setTrafficArrow();
                    lane.Type = LaneType.DRIVEWAY;
                }
                else //no traffic light --> exit lane
                {
                    lane.Type = LaneType.EXIT;
                }
                lane.setInterSection(this);
            }
            return roadList;
        }

        private Lane getFowardExitLane(int exitOrientation, Point carCenterPoint)
        {
            Road exitRoad = getExitRoadByOrientation(exitOrientation);
            foreach (Lane lane in exitRoad.getLanes())
                if (((exitOrientation == 0 || exitOrientation == 180) && Toolkit.elementIsAtX(lane, carCenterPoint.X)) ||
                    ((exitOrientation == 90 || exitOrientation == 270) && Toolkit.elementIsAtY(lane, carCenterPoint.Y)))
                    return lane;
            return null;
        }

        private  Lane getTurnExitLane(DirectionData directionData, Car car)
        {
            int exitOrientation = (directionData.getDirection() == Direction.LEFT) ?
                Toolkit.alterOrientationBy(directionData.getOrientation(), -90) : Toolkit.alterOrientationBy(directionData.getOrientation(), 90);

            int entryLaneNumber = getEntryLaneNumber(directionData.getDirection(), car);
            Road exitRoad = getExitRoadByOrientation(exitOrientation);
            if (directionData.getDirection() == Direction.LEFT)
                return Road.getLeftLaneFromLanes(exitRoad.getLanes(), entryLaneNumber);
            else
                return Road.getRightLaneFromLanes(exitRoad.getLanes(), entryLaneNumber);
        }

        private int getEntryLaneNumber(Direction direction, Car car)
        {
            Road entryRoad = Map.INSTANCE.getRoadAtPoint(car.getRearBumperPoint());
            Lane entryLane = Map.INSTANCE.getLaneAtPoint(car.getRearBumperPoint());
            return entryRoad.getLaneNumberByDirection(entryLane, direction);
        }

        private Road getExitRoadByOrientation(int orientation)
        {
            return getExitRoadBySide(Toolkit.orientationToSide(orientation));
        }

        private Road getExitRoadBySide(Side side)
        {
            if (!roadDictionary.ContainsKey(side)) throw new InvalidOperationException("This intersection does not have any roads on that side");
            List<Road> roadList = roadDictionary[side];
            Road road = null;
            foreach (Road nextRoad in roadList)
                if (nextRoad.getLanes()[0].Type == LaneType.EXIT)
                {
                    road = nextRoad;
                    break;
                }
            return road;
        }
    }
}
